﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using mshtml;
using System.Collections.ObjectModel;
using System.Timers;
using System.Windows.Threading;

namespace Smith.WPF.HtmlEditor
{
    public partial class HtmlEditor : UserControl
    {
        HtmlDocument htmldoc;
        Window hostedWindow;
        DispatcherTimer styleTimer;
        Dictionary<string, ImageObject> imageDic;
        EditMode mode;

        public HtmlEditor()
        {
            InitializeComponent();
            InitContainer();
            InitFontFamilies();
            InitFontSizes();
            InitEvents();
            InitTimer();
        }

        /// <summary>
        /// 获取统计字符数
        /// </summary>
        public int WordCount
        {
            get
            {
                if (ToggleCodeMode.IsChecked == true)
                {
                    WordCounter counter = WordCounter.Create();
                    return counter.Count(CodeEditor.Text);
                }
                else if (htmldoc != null && htmldoc.Content != null)
                {
                    WordCounter counter = WordCounter.Create();
                    return counter.Count(htmldoc.Text);
                }
                return 0;
            }
        }

        /// <summary>
        /// 获取或设置编辑器中的HTML内容
        /// </summary>
        public string ContentHtml
        {
            get
            {
                if (ToggleCodeMode.IsChecked == true)
                    VisualEditor.Document.Body.InnerHtml = CodeEditor.Text;
                return VisualEditor.Document.Body.InnerHtml;
            }
            set
            {
                VisualEditor.Document.Body.InnerHtml = (value != null ? value : string.Empty);
                if (ToggleCodeMode.IsChecked == true)
                    CodeEditor.Text = VisualEditor.Document.Body.InnerHtml;                
            }
        }

        /// <summary>
        /// 获取编辑器中的文本内容
        /// </summary>
        public string ContentText
        {
            get
            {
                if (ToggleCodeMode.IsChecked == true)
                    VisualEditor.Document.Body.InnerHtml = CodeEditor.Text;
                return VisualEditor.Document.Body.InnerText;
            }
        }

        #region EditMode Dependency Property

        /// <summary>
        /// 获取或设置编辑器模式，这是一个依赖属性
        /// </summary>
        public EditMode EditMode
        {
            get { return (EditMode)GetValue(EditModeProperty); }
            set { SetValue(EditModeProperty, value); }
        }

        public static readonly DependencyProperty EditModeProperty =
            DependencyProperty.Register("DependencyEditMode", typeof(EditMode), typeof(HtmlEditor),
                new FrameworkPropertyMetadata(EditMode.Visual, new PropertyChangedCallback(OnDependancyEditModeChanged)));

        static void OnDependancyEditModeChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            HtmlEditor editor = (HtmlEditor)sender;
            if ((EditMode)e.NewValue == EditMode.Visual) editor.SetVisualMode();
            else editor.SetSourceMode();
        } 

        #endregion

        /// <summary>
        /// 获取编辑器中的HTML文档
        /// </summary>
        public HtmlDocument Document
        {
            get { return htmldoc; }
        }

        #region Document Ready Event

        public static readonly RoutedEvent DocumentReadyEvent =
            EventManager.RegisterRoutedEvent("DocumentReady", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(HtmlEditor));

        public event RoutedEventHandler DocumentReady
        {
            add { base.AddHandler(DocumentReadyEvent, value); }
            remove { base.RemoveHandler(DocumentReadyEvent, value); }
        }

        #endregion

        #region Document State Changed Event

        public static readonly RoutedEvent DocumentStateChangedEvent =
            EventManager.RegisterRoutedEvent("DocumentStateChanged", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(HtmlEditor));

        public event RoutedEventHandler DocumentStateChanged
        {
            add { base.AddHandler(DocumentStateChangedEvent, value); }
            remove { base.RemoveHandler(DocumentStateChangedEvent, value); }
        }

        #endregion

        #region 初始化事件

        void InitEvents()
        {
            this.Loaded += new RoutedEventHandler(HtmlEditor_Loaded);
            this.Unloaded += new RoutedEventHandler(HtmlEditor_Unloaded);            
            ToggleFontColor.Click += new RoutedEventHandler(ToggleFontColor_Click);
            ToggleLineColor.Click += new RoutedEventHandler(ToggleLineColor_Click);
            ToggleCodeMode.Checked += new RoutedEventHandler(ToggleCodeMode_Checked);
            ToggleCodeMode.Unchecked += new RoutedEventHandler(ToggleCodeMode_Unchecked);
            FontColorContextMenu.Opened += new RoutedEventHandler(FontColorContextMenu_Opened);
            FontColorContextMenu.Closed += new RoutedEventHandler(FontColorContextMenu_Closed);
            LineColorContextMenu.Opened += new RoutedEventHandler(LineColorContextMenu_Opened);
            LineColorContextMenu.Closed += new RoutedEventHandler(LineColorContextMenu_Closed);
            FontColorPicker.SelectedColorChanged += new EventHandler<PropertyChangedEventArgs<Color>>(FontColorPicker_SelectedColorChanged);
            LineColorPicker.SelectedColorChanged += new EventHandler<PropertyChangedEventArgs<Color>>(LineColorPicker_SelectedColorChanged);
        }

        void ToggleCodeMode_Checked(object sender, RoutedEventArgs e)
        {
            EditMode = EditMode.Source;
        }

        void ToggleCodeMode_Unchecked(object sender, RoutedEventArgs e)
        {
            EditMode = EditMode.Visual;
        }

        void HtmlEditor_Loaded(object sender, RoutedEventArgs e)
        {
            imageDic = new Dictionary<string, ImageObject>();
            this.hostedWindow = this.GetParentWindow();
            styleTimer.Start();
        }

        void HtmlEditor_Unloaded(object sender, RoutedEventArgs e)
        {
            styleTimer.Stop();
        }

        void ToggleFontColor_Click(object sender, RoutedEventArgs e)
        {
            FrameworkElement fxElement = sender as FrameworkElement;
            if (fxElement != null && FontColorContextMenu != null)
            {
                FontColorContextMenu.PlacementTarget = fxElement;
                FontColorContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
                FontColorContextMenu.IsOpen = true;
            }
        }

        void ToggleLineColor_Click(object sender, RoutedEventArgs e)
        {
            FrameworkElement fxElement = sender as FrameworkElement;
            if (fxElement != null && LineColorContextMenu != null)
            {
                LineColorContextMenu.PlacementTarget = fxElement;
                LineColorContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
                LineColorContextMenu.IsOpen = true;
            }
        }

        void FontColorContextMenu_Opened(object sender, RoutedEventArgs e)
        {
            FontColorPicker.Reset();
            ToggleFontColor.IsChecked = true;
        }

        void FontColorContextMenu_Closed(object sender, RoutedEventArgs e)
        {
            ToggleFontColor.IsChecked = false;
        }

        void LineColorContextMenu_Opened(object sender, RoutedEventArgs e)
        {
            LineColorPicker.Reset();
            ToggleLineColor.IsChecked = true;
        }

        void LineColorContextMenu_Closed(object sender, RoutedEventArgs e)
        {
            ToggleLineColor.IsChecked = false;
        }

        void FontColorPicker_SelectedColorChanged(object sender, PropertyChangedEventArgs<Color> e)
        {
            htmldoc.SetFontColor(e.NewValue);
        }

        void LineColorPicker_SelectedColorChanged(object sender, PropertyChangedEventArgs<Color> e)
        {
            htmldoc.SetLineColor(e.NewValue);
        }

        #endregion

        #region 初始化编辑器

        bool isDocReady;
        RoutedEventArgs DocumentStateChangedEventArgs = new RoutedEventArgs(HtmlEditor.DocumentStateChangedEvent);

        void InitContainer()
        {
            VisualEditor.Navigated += this.VisualEditor_Navigated;
            VisualEditor.StatusTextChanged += this.VisualEditor_StatusTextChanged;
            VisualEditor.DocumentText = "<html><body></body></html>";
        }

        void VisualEditor_StatusTextChanged(object sender, EventArgs e)
        {
			try
			{
				RaiseEvent(DocumentStateChangedEventArgs);
				if (Document.State == HtmlDocumentState.Complete && isDocReady != true)
				{
					isDocReady = true;
					RaiseEvent(new RoutedEventArgs(HtmlEditor.DocumentReadyEvent));
					VisualEditor.Document.Focus();
				}
			}
			catch { }
        }

        void VisualEditor_Navigated(object sender, System.Windows.Forms.WebBrowserNavigatedEventArgs e)
        {            
            VisualEditor.Document.ContextMenuShowing += this.DocumentContextMenuShowing;
            htmldoc = new HtmlDocument(VisualEditor.Document);            
            ((IHTMLDocument2)VisualEditor.Document.DomDocument).designMode = "ON";
            VisualEditor.Document.Focus();
        }

        void DocumentContextMenuShowing(object sender, System.Windows.Forms.HtmlElementEventArgs e)
        {
            EditingContextMenu.IsOpen = true;
            e.ReturnValue = false;
        }

        void SetVisualMode()
        {
            if (mode != EditMode.Visual)
            {
                BrowserHost.Visibility = Visibility.Visible;
                CodeEditor.Visibility = Visibility.Hidden;

                FontFamilyList.IsEnabled = true;
                FontSizeList.IsEnabled = true;
                ToggleFontColor.IsEnabled = true;
                ToggleLineColor.IsEnabled = true;

                VisualEditor.Document.Body.InnerHtml = GetEditContent();
                mode = EditMode.Visual;
            }
        }

        void SetSourceMode()
        {
            if (mode != EditMode.Source)
            {
                BrowserHost.Visibility = Visibility.Hidden;
                CodeEditor.Visibility = Visibility.Visible;

                FontFamilyList.IsEnabled = false;
                FontSizeList.IsEnabled = false;
                ToggleFontColor.IsEnabled = false;
                ToggleLineColor.IsEnabled = false;

                CodeEditor.Text = GetEditContent();
                mode = EditMode.Source;
            }
        }

        string GetEditContent()
        {
            switch (mode)
            {
                case EditMode.Visual:
                    return VisualEditor.Document.Body.InnerHtml;
                default:
                    return CodeEditor.Text;
            }
        }

        #endregion

        #region 初始化定时器

        void InitTimer()
        {
            styleTimer = new DispatcherTimer();
            styleTimer.Interval = TimeSpan.FromMilliseconds(200);
            styleTimer.Tick += new EventHandler(timer_Tick);
        }

        void timer_Tick(object sender, EventArgs e)
        {
            if (htmldoc.State != HtmlDocumentState.Complete) return;

            ToggleBold.IsChecked = htmldoc.IsBold();
            ToggleItalic.IsChecked = htmldoc.IsItalic();
            ToggleUnderline.IsChecked = htmldoc.IsUnderline();
            ToggleSubscript.IsChecked = htmldoc.IsSubscript();
            ToggleSuperscript.IsChecked = htmldoc.IsSuperscript();
            ToggleBulletedList.IsChecked = htmldoc.IsBulletsList();
            ToggleNumberedList.IsChecked = htmldoc.IsNumberedList();
            ToggleJustifyLeft.IsChecked = htmldoc.IsJustifyLeft();
            ToggleJustifyRight.IsChecked = htmldoc.IsJustifyRight();
            ToggleJustifyCenter.IsChecked = htmldoc.IsJustifyCenter();
            ToggleJustifyFull.IsChecked = htmldoc.IsJustifyFull();

            FontFamilyList.SelectedItem = htmldoc.GetFontFamily();
            FontSizeList.SelectedItem = htmldoc.GetFontSize();
        }

        #endregion

        #region 初始化字体

        void InitFontFamilies()
        {
            FontFamilyList.ItemsSource = GetDefaultFontFamilies();
            FontFamilyList.SelectionChanged += new SelectionChangedEventHandler(FontFamilyList_SelectionChanged);
        }

        void FontFamilyList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (htmldoc != null)
            {
                FontFamily selectionFontFamily = htmldoc.GetFontFamily();
                FontFamily selectedFontFamily = (FontFamily)FontFamilyList.SelectedValue;
                if (selectedFontFamily != selectionFontFamily) htmldoc.SetFontFamily(selectedFontFamily);
            }
        }

        ReadOnlyCollection<FontFamily> GetDefaultFontFamilies()
        {
            List<FontFamily> ls = new List<FontFamily>();
            ls.Add(new FontFamily("Arial"));
            ls.Add(new FontFamily("Calibri"));
            ls.Add(new FontFamily("Cambria"));
            ls.Add(new FontFamily("Candara"));
            ls.Add(new FontFamily("Century"));
            ls.Add(new FontFamily("Consolas"));
            ls.Add(new FontFamily("Constantia"));
            ls.Add(new FontFamily("Corbel"));
            ls.Add(new FontFamily("Courier New"));
            ls.Add(new FontFamily("Microsoft Sans Serif"));
            ls.Add(new FontFamily("MS Reference Sans Serif"));
            ls.Add(new FontFamily("Segoe UI"));
            ls.Add(new FontFamily("Tahoma"));
            ls.Add(new FontFamily("Times New Roman"));
            ls.Add(new FontFamily("Verdana"));
            ls.Add(new FontFamily("Vrinda"));
            ls.Add(new FontFamily("黑体"));
            ls.Add(new FontFamily("楷体"));
            ls.Add(new FontFamily("隶书"));
            ls.Add(new FontFamily("宋体"));
            ls.Add(new FontFamily("微软雅黑"));
            ls.Add(new FontFamily("新宋体"));
            return new ReadOnlyCollection<FontFamily>(ls);
        } 

        #endregion

        #region 初始化字体大小

        void InitFontSizes()
        {
            FontSizeList.ItemsSource = GetDefaultFontSizes();
            FontSizeList.SelectionChanged += new SelectionChangedEventHandler(FontSizeList_SelectionChanged);
        }

        void FontSizeList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (htmldoc != null)
            {
                FontSize selectionFontSize = htmldoc.GetFontSize();
                FontSize selectedFontSize = (FontSize)FontSizeList.SelectedValue;
                if (selectedFontSize != selectionFontSize) htmldoc.SetFontSize(selectedFontSize);
            }
        }

        ReadOnlyCollection<FontSize> GetDefaultFontSizes()
        {
            List<FontSize> ls = new List<FontSize>()
            {
                Smith.WPF.HtmlEditor.FontSize.XXSmall,
                Smith.WPF.HtmlEditor.FontSize.XSmall,
                Smith.WPF.HtmlEditor.FontSize.Small,
                Smith.WPF.HtmlEditor.FontSize.Middle,
                Smith.WPF.HtmlEditor.FontSize.Large,
                Smith.WPF.HtmlEditor.FontSize.XLarge,
                Smith.WPF.HtmlEditor.FontSize.XXLarge
            };
            return new ReadOnlyCollection<FontSize>(ls);
        } 

        #endregion

        #region Properties

        /// <summary>
        /// 是否可执行撤销命令
        /// </summary>
        public bool CanUndo
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Undo"); }
        }

        /// <summary>
        /// 是否可执行重做命令
        /// </summary>
        public bool CanRedo
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Redo"); }
        }

        /// <summary>
        /// 是否可执行剪切命令
        /// </summary>
        protected bool CanCut
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Cut"); }
        }

        /// <summary>
        /// 是否可执行复制命令
        /// </summary>
        protected bool CanCopy
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Copy"); }
        }

        /// <summary>
        /// 是否可执行粘贴命令
        /// </summary>
        protected bool CanPaste
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Paste"); }
        }

        /// <summary>
        /// 是否可执行删除命令
        /// </summary>
        protected bool CanDelete
        {
            get { return mode == EditMode.Visual && htmldoc != null && htmldoc.QueryCommandEnabled("Delete"); }
        }

        #endregion

        #region 执行命令

        /// <summary>
        /// 执行撤销命令
        /// </summary>
        public void Undo()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("Undo", false, null);
        }

        /// <summary>
        /// 执行重做命令
        /// </summary>
        public void Redo()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("Redo", false, null);
        }

        /// <summary>
        /// 执行剪切命令
        /// </summary>
        public void Cut()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("Cut", false, null);
        }

        /// <summary>
        /// 执行复制命令
        /// </summary>
        public void Copy()
        {
            if (htmldoc != null)
                htmldoc.ExecuteCommand("Copy", false, null);
        }

        /// <summary>
        /// 执行粘贴命令
        /// </summary>
        public void Paste()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("Paste", false, null);
        }

        /// <summary>
        /// 执行删除命令
        /// </summary>
        public void Delete()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("Delete", false, null);
        }

        /// <summary>
        /// 执行全选命令
        /// </summary>
        public void SelectAll()
        {
            if (htmldoc != null) 
                htmldoc.ExecuteCommand("SelectAll", false, null);
        }

        #endregion

        #region 命令绑定

        private void Undo_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Undo();
        }

        private void Undo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanUndo;
        }

        private void Redo_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Redo();
        }

        private void Redo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanRedo;
        }

        private void Cut_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Cut();
        }

        private void Cut_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanCut;
        }

        private void Copy_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Copy();
        }

        private void Copy_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanCopy;
        }

        private void Paste_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Paste();
        }

        private void Paste_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanPaste;
        }

        private void Delete_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Delete();
        }

        private void Delete_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanDelete;
        }

        private void SelectAll_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            SelectAll();
        }

        private void Bold_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Bold();
        }

        private void Italic_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Italic();
        }

        private void Underline_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Underline();
        }

        private void Subscript_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Subscript();
        }

        private void Subscript_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (mode == EditMode.Visual && htmldoc != null && htmldoc.CanSubscript());
        }

        private void Superscript_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Superscript();
        }

        private void Superscript_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (mode == EditMode.Visual && htmldoc != null && htmldoc.CanSuperscript());
        }

        private void ClearStyle_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.ClearStyle();
        }

        private void Indent_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Indent();
        }

        private void Outdent_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.Outdent();
        }

        private void BubbledList_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.BulletsList();
        }

        private void NumericList_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.NumberedList();
        }

        private void JustifyLeft_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.JustifyLeft();
        }

        private void JustifyRight_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.JustifyRight();
        }

        private void JustifyCenter_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.JustifyCenter();
        }

        private void JustifyFull_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null) htmldoc.JustifyFull();
        }

        private void EditingCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = (htmldoc != null && mode == EditMode.Visual);
        }

        /// <summary>
        /// 插入超链接事件
        /// </summary>
        private void InsertHyperlink_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null)
            {
                HyperlinkDialog d = new HyperlinkDialog();
                d.Owner = this.hostedWindow;
                d.Model = new HyperlinkObject { URL = "http://", Text = htmldoc.Selection.Text };
                if (d.ShowDialog() == true)
                {
                    htmldoc.InsertHyperlick(d.Model);
                }
            }
        }

        /// <summary>
        /// 插入图像事件
        /// </summary>
        private void InsertImage_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null)
            {
                ImageDialog d = new ImageDialog();
                d.Owner = this.hostedWindow;
                if (d.ShowDialog() == true)
                {
                    htmldoc.InsertImage(d.Model);
                    imageDic[d.Model.ImageUrl] = d.Model;
                }
            }
        }

        /// <summary>
        /// 插入表格事件
        /// </summary>
        private void InsertTable_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (htmldoc != null)
            {
                TableDialog d = new TableDialog();
                d.Owner = this.hostedWindow;
                if (d.ShowDialog() == true)
                {
                    htmldoc.InsertTable(d.Model);
                }
            }
        }

        private void InsertCodeBlock_Executed(object sender, ExecutedRoutedEventArgs e)
        {

        } 

        #endregion
    }
}
